iT邦幫忙

2024 iThome 鐵人賽

DAY 2
0

https://ithelp.ithome.com.tw/upload/images/20241013/2016930945qOKw2C2l.png
這題要實作一個 LRU (Least Recently Used) cache,就是當超過容量時,會淘汰最久沒被用的元素,需要實作一個 LRUCache 類別,要有兩個主要功能:get(key)返回對應鍵的值,如果不存在就返回 -1、put(key, value)如果鍵存在,更新其值;如果鍵不存在,就把新的鍵值對插到 cache 中,如果插入導致 cache 超過容量,就要移除最少用的鍵,為了達到 O(1) 的時間複雜度,用兩個資料結構是比較保險,哈希表 (HashMap)用來快速檢索鍵對應的值,雙向鏈表,用來追蹤最近使用順序,最左是最久沒用的,最右是最新用的,雙向鏈表的設計允許在 O(1) 時間內把元素移到最前面,或從尾刪元素。

步驟:
設計雙向鏈表,每個節點包含 key、value、prev 和 next,用來記前後關係,這樣可以在 O(1) 時間內把任意節點移動或刪除,HashMap (字典),用來存 key 到對應鏈表節點的映射,方便快速查詢。
操作get ,如果鍵存在字典中,就移動該節點到鏈表最前面,在返回其值,如果鍵不存在,返回 -1。
操作put ,如果鍵已經存在,更新該節點值再把它移到最前面,如果鍵不存在,創一個新節點再插到鏈表最前面,如果超過容量,就刪鏈表末尾節點。
程式碼:
class ListNode:
def init(self, key=0, value=0):
self.key = key
self.value = value
self.prev = None
self.next = None

class LRUCache(object):

def __init__(self, capacity):
    """
    :type capacity: int
    """
    self.capacity = capacity
    self.cache = {}  # 儲存 key -> node 映射
    self.head = ListNode()  # 虛擬頭節點
    self.tail = ListNode()  # 虛擬尾節點
    self.head.next = self.tail
    self.tail.prev = self.head

def get(self, key):
    """
    :type key: int
    :rtype: int
    """
    if key in self.cache:
        node = self.cache[key]
        self._move_to_front(node)
        return node.value
    return -1

def put(self, key, value):
    """
    :type key: int
    :type value: int
    :rtype: None
    """
    if key in self.cache:
        node = self.cache[key]
        node.value = value
        self._move_to_front(node)
    else:
        if len(self.cache) >= self.capacity:
            self._remove_last()
        new_node = ListNode(key, value)
        self._add_to_front(new_node)
        self.cache[key] = new_node

def _move_to_front(self, node):
    # 把節點移到鏈表的最前面
    self._remove(node)
    self._add_to_front(node)

def _remove(self, node):
    # 從鏈表中移除節點
    prev_node = node.prev
    next_node = node.next
    prev_node.next = next_node
    next_node.prev = prev_node

def _add_to_front(self, node):
    # 把節點加到鏈表最前面
    next_node = self.head.next
    self.head.next = node
    node.prev = self.head
    node.next = next_node
    next_node.prev = node

def _remove_last(self):
    # 移除鏈表最後一個節點
    last_node = self.tail.prev
    self._remove(last_node)
    del self.cache[last_node.key]

解釋:
初始化,創一個容量為 capacity 的快取,設雙向鏈表的虛擬頭和尾來方便管理節點,get 操作,如果鍵存在於快取中,把它對應節點移到最前面並返回值,如果不存在,返回 -1,put 操作,如果鍵已存在,更新值並把節點移到最前面,如果鍵不存在,插入新節點,如果容量超過上限,移掉最久沒用的節點。
輔助:_move_to_front 負責把節點移到鏈表最前面,_remove 和 _add_to_front 負責在雙向鏈表中插入或移除節點,_remove_last 用來刪最久沒用的節點。
心得:
這題是資料結構設計問題,解決方案涉及對雙向鏈表和哈希表的應用,對每個操作,要求 O(1) 的時間複雜度,所以我選擇這兩個結構配合用,這題幫我鞏固雙向鏈表的實作跟它和哈希表的結合,尤其是怎麼設計高效的資料結構來滿足實際應用,學到關鍵技巧是怎樣通過雙向鏈表來追蹤元素的使用順序,並實現 LRU 這樣一個具實際應用意義的策略。


上一篇
D26:Q142Linked List Cycle II
下一篇
D28:Q154Find Minimum in Rotated Sorted Array II
系列文
Leecode刷題28
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言